/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or ( at your option ) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
aLONG with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

/**
 * system & local includes
 **/
#include "commonheaders.h"
#include "ctrl.h"

#define LVF_EDITLABEL	8

#define CELIF_CUSTOM	8		// entry is userdefined ( e.g. MyPhoneXX )
#define CELIF_SMS		16		// phone with ability to receive sms
#define LIF_TIPVISIBLE	32		// set if infotip is visible

typedef struct TCECListItem : CTRL {
	LPIDSTRLIST	idstrList;
	INT			idstrListCount;
	INT			iListItem;
	LPTSTR		pszText[2];
} LCITEM, *LPLCITEM;

typedef struct TListCtrl : CTRL {
	HWND		hList;
	HWND		hTip;
	POINT		ptTip;
	INT			iHotItem;
	INT			iHotSubItem;
	HFONT		hFont;

	struct {
		HWND		hEdit;		// handle to edit window
		HWND		hBtn;		// button to open dropdown list
		RECT		rcCombo;
		struct {
			HWND	hDrop;		// dropdown list
			INT		iItem;		// currently selected item of the dropdown
		} dropDown; 
		LPLCITEM	pItem;		// the item beiing edited
		INT			iItem;		// zero based index to item in the listview
		INT			iSubItem;	// zero based index to subitem
		INT			iTopIndex;	// zero based index to first visible item on list
	} labelEdit;
} LISTCTRL, *LPLISTCTRL;

typedef INT ( *MISERVICE )( WPARAM wParam, LPARAM lParam );

typedef struct TProfileEntries {
	LPTSTR	szGroup;
	LPCSTR	szCatFmt;
	LPCSTR	szValFmt;
	MISERVICE	GetList;
} PROFILEENTRY, *LPPROFILEENTRY;

static const PROFILEENTRY pFmt[3] = {
	{ LPGENT( "Past" ),			"Past%d",			"Past%dText",		 GetPastList			},
	{ LPGENT( "Affiliation" ),	"Affiliation%d",	"Affiliation%dText", GetAffiliationsList	},
	{ LPGENT( "Interest" ),		"Interest%dCat",	"Interest%dText",	 GetInterestsList		}
};

static	WNDPROC		OldListViewProc = NULL;	// listview control's default window procedure
static	WNDPROC		OldEditProc = NULL;		// edit control's default window procedure
static	WNDPROC		OldDropdownProc = NULL;	// listbox control's default window procedure

static LRESULT CALLBACK ProfileList_LabelEditProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );

/**
 * name:	ProfileList_AddGroup
 * desc:	add a group to the listview
 * param:	hList	- handle to listview control
 *			pszText	- text of new group
 * return:	index the where group was added
 **/
static INT ProfileList_AddGroup( HWND hList, LPTSTR pszText, INT iItem )
{
	LVITEM lvi;
	lvi.mask = LVIF_TEXT|LVIF_PARAM;
	lvi.iItem = iItem;
	lvi.iSubItem = 0;
	lvi.pszText = pszText;
	lvi.lParam = NULL;
	return ListView_InsertItem( hList, &lvi );
}

/**
 * name:	ProfileList_GetItemText
 * desc:	returns the text of a listview item
 * param:	hList	 - handle to listview control
 *			iItem	 - item index
 *			iSubItem - subitem ( column ) index
 *			pszText	 - pointer of a buffer to retrieve the text
 *			ccText	 - number of maximal characters pszText can take
 * return:	number of read characters
 **/
static INT ProfileList_GetItemText( HWND hList, INT iItem, INT iSubItem, LPTSTR pszText, INT ccText )
{
	LVITEM lvi;
	TCHAR szNull[2];

	lvi.mask = LVIF_TEXT;
	lvi.iItem = iItem;
	lvi.iSubItem = iSubItem;
	lvi.cchTextMax = ccText ? ccText : 2;
	lvi.pszText = pszText ? pszText : szNull;
	return SNDMSG( hList, LVM_GETITEMTEXT, iItem, ( LPARAM )&lvi );
}

/**
 * name:	ProfileList_GetItemData
 * desc:	return the infostructure associated with the desired item
 * param:	hList	- handle to listview control
 *			iItem	- item index
 * return:	LPLCITEM structure
 **/
static LPLCITEM ProfileList_GetItemData( HWND hList, INT iItem )
{
	LVITEM lvi;

	if( iItem < 0 ) return NULL;
	lvi.mask = LVIF_PARAM;
	lvi.iSubItem = 0;
	lvi.iItem = iItem;
	return ( SendMessage( hList, LVM_GETITEM, NULL, ( LPARAM )&lvi ) ) ? ( LPLCITEM )lvi.lParam : NULL;
}

/**
 * name:	ProfileList_DeleteItem
 * desc:	delete an item from the listview
 * param:	hList	- handle to listview control
 *			iItem	- item index
 *	
 * return:	nothing
 **/
static VOID ProfileList_DeleteItem( HWND hList, INT iItem )
{
	LPLCITEM pItem = ProfileList_GetItemData( hList, iItem );
	
	if( PtrIsValid( pItem ) ) {
		if( pItem->pszText[0] )
			mir_free( pItem->pszText[0] );
		if( pItem->pszText[1] )
			mir_free( pItem->pszText[1] );
		free( pItem );
	}
	ListView_DeleteItem( hList, iItem );
}

/**
 * name:	ProfileList_Clear
 * desc:	delete all list items and their data
 *
 **/
static VOID ProfileList_Clear( HWND hList )
{
	LVITEM lvi;

	lvi.iItem = 0;
	lvi.iSubItem = 0;
	lvi.mask = LVIF_PARAM;

	while( ListView_GetItem( hList, &lvi ) ) {
		if( PtrIsValid( lvi.lParam ) ) {
			if( (( LPLCITEM )lvi.lParam )->pszText[0] )
				mir_free( (( LPLCITEM )lvi.lParam )->pszText[0] );
			if( (( LPLCITEM )lvi.lParam )->pszText[1] )
				mir_free( (( LPLCITEM )lvi.lParam )->pszText[1] );
			free( ( LPVOID )lvi.lParam );
		}
		lvi.iItem++;
	}
	ListView_DeleteAllItems( hList );
}

/**
 * name:	ProfileList_EndLabelEdit
 * desc:	destroys the edit control and saves the text to the list control if desired
 * param:	hList	- handle to listview control
 *			bSave	- tells, whether to save changes ( TRUE ) or not ( FALSE )
 * return:	returns 0 on success or nonzero
 **/
static INT ProfileList_EndLabelEdit( LPLISTCTRL pList, BOOLEAN bSave )
{
	HWND hEdit;

	// check if labeledit is enabled
	if( !PtrIsValid( pList ) || !pList->hList || !pList->labelEdit.hEdit )
		return 1;
	// set hEdit NULL to indicate the endlabeledit call and prevent other calls
	hEdit = pList->labelEdit.hEdit;
	pList->labelEdit.hEdit = NULL;

	if( bSave != FALSE && pList->labelEdit.pItem ) {
		WORD ccText;
		LPTSTR  szEdit = NULL;
		BOOLEAN bChanged = FALSE;

		// an list element was selected
		if( pList->labelEdit.iSubItem && pList->labelEdit.dropDown.iItem != pList->labelEdit.pItem->iListItem && pList->labelEdit.dropDown.iItem >= 0 && pList->labelEdit.dropDown.iItem < pList->labelEdit.pItem->idstrListCount ) {
			pList->labelEdit.pItem->iListItem = pList->labelEdit.dropDown.iItem;
			if( pList->labelEdit.pItem->pszText[0] ) {
				mir_free( pList->labelEdit.pItem->pszText[0] );
				pList->labelEdit.pItem->pszText[0] = NULL;
			}
			bChanged = TRUE;
		}
		// value was edited
		else {
			if( ( ccText = GetWindowTextLength( hEdit ) ) > 0 && ( szEdit = ( LPTSTR )mir_alloc( ( ccText + 2 ) * sizeof( TCHAR ) ) ) ) {
				GetWindowText( hEdit, szEdit, ccText + 1 );
				szEdit[ccText + 1] = 0;

				if( !pList->labelEdit.pItem->pszText[pList->labelEdit.iSubItem] ) {
					pList->labelEdit.pItem->pszText[pList->labelEdit.iSubItem] = szEdit;
					bChanged = TRUE;
				}
				else
				if( _tcscmp( pList->labelEdit.pItem->pszText[pList->labelEdit.iSubItem], szEdit ) ) {
					mir_free( pList->labelEdit.pItem->pszText[pList->labelEdit.iSubItem] );
					pList->labelEdit.pItem->pszText[pList->labelEdit.iSubItem] = szEdit;
					bChanged = TRUE;
				}
				else
					mir_free( szEdit );
			}
		}
		if( bChanged ) {
			pList->labelEdit.pItem->wFlags |= CTRLF_CHANGED;
			pList->wFlags |= CTRLF_CHANGED;
			SendMessage( GetParent( GetParent( pList->hList ) ), PSM_CHANGED, 0, 0 );
		}
	}
	if( pList->labelEdit.hBtn ) DestroyWindow( pList->labelEdit.hBtn );
	if( pList->labelEdit.dropDown.hDrop ) DestroyWindow( pList->labelEdit.dropDown.hDrop );
	DestroyWindow( hEdit );
	ListView_RedrawItems( pList->hList, pList->labelEdit.iItem, pList->labelEdit.iItem );
	ZeroMemory( &pList->labelEdit, sizeof( pList->labelEdit ) );
	SetFocus( pList->hList );
	return 0;
}

static INT ProfileList_EndLabelEdit( HWND hList, BOOLEAN bSave )
{
	return ProfileList_EndLabelEdit( ( LPLISTCTRL )GetUserData( hList ), bSave );
}

/**
 * name:	ProfileList_BeginLabelEdit
 * desc:	create an edit control to edit the label of the selected item
 * param:	pList	 - handle to listview control's info structure
 *			iItem	 - item index
 *			iSubItem - subitem ( column ) index
 * return:	handle to the edit control
 **/
static HWND ProfileList_BeginLabelEdit( LPLISTCTRL pList, INT iItem, INT iSubItem )
{
	LVITEM lvi;
	LPLCITEM pItem;
	HANDLE hContact;
	RECT rcList;
	
	if( !PtrIsValid( pList ) ) 
		return NULL;
	if( pList->labelEdit.hEdit )
		ProfileList_EndLabelEdit( pList, FALSE );

	lvi.mask = LVIF_PARAM|LVIF_STATE;
	lvi.stateMask = 0xFFFFFFFF;
	lvi.iItem = iItem;
	lvi.iSubItem = iSubItem;

	if( !ListView_GetItem( pList->hList, &lvi ) )
		return NULL;

	pItem = ( LPLCITEM )lvi.lParam;

	PSGetContact( GetParent( pList->hList ), hContact );
			
	// do not edit deviders or protocol based contact information
	if( !( lvi.state & LVIS_SELECTED ) || !PtrIsValid( pItem ) || ( hContact && ( pItem->wFlags & CTRLF_HASPROTO ) ) ) 
		return NULL;

	ListView_EnsureVisible( pList->hList, iItem, FALSE );
	ListView_GetSubItemRect( pList->hList, iItem, iSubItem, LVIR_BOUNDS, &pList->labelEdit.rcCombo );
		
	if( lvi.iSubItem == 0 ) {
		RECT rc2;
		
		ListView_GetSubItemRect( pList->hList, iItem, 1, LVIR_BOUNDS, &rc2 );
		pList->labelEdit.rcCombo.right = rc2.left;
	}
	GetClientRect( pList->hList, &rcList );
	pList->labelEdit.rcCombo.right = min( pList->labelEdit.rcCombo.right, rcList.right );
	pList->labelEdit.rcCombo.left = max( pList->labelEdit.rcCombo.left, rcList.left );
	InflateRect( &pList->labelEdit.rcCombo, -1, -1 );

	// create the button control for the combobox
	if( !iSubItem && pItem->idstrList ) {
		pList->labelEdit.hBtn = CreateWindowEx( WS_EX_NOPARENTNOTIFY, UINFOBUTTONCLASS, NULL, 
					WS_VISIBLE|WS_CHILD|MBS_DOWNARROW,
					pList->labelEdit.rcCombo.right - ( pList->labelEdit.rcCombo.bottom - pList->labelEdit.rcCombo.top ), pList->labelEdit.rcCombo.top,
					pList->labelEdit.rcCombo.bottom - pList->labelEdit.rcCombo.top,
					pList->labelEdit.rcCombo.bottom - pList->labelEdit.rcCombo.top,
					pList->hList, NULL, ghInst, NULL );
		if( pList->labelEdit.hBtn ) {
			SetWindowLongPtr( pList->labelEdit.hBtn, GWL_ID, BTN_EDIT );
			pList->labelEdit.rcCombo.right -= pList->labelEdit.rcCombo.bottom - pList->labelEdit.rcCombo.top;
		}
	}
	else {
		pList->labelEdit.rcCombo.bottom = 3 * pList->labelEdit.rcCombo.bottom - 2 * pList->labelEdit.rcCombo.top;
		if( rcList.bottom < pList->labelEdit.rcCombo.bottom ) {
			OffsetRect( &pList->labelEdit.rcCombo, 0, rcList.bottom - pList->labelEdit.rcCombo.bottom - 2 );
		}
	}
	// create the edit control
	pList->labelEdit.hEdit = CreateWindowEx( WS_EX_NOPARENTNOTIFY|WS_EX_CLIENTEDGE,
				_T( "EDIT" ),
				( !iSubItem && pItem->idstrList && pItem->iListItem > 0 && pItem->iListItem < pItem->idstrListCount ) 
					? pItem->idstrList[pItem->iListItem].ptszTranslated
					: ( iSubItem >= 0 && iSubItem < 2 && pItem->pszText[iSubItem] && *pItem->pszText[iSubItem] )
						? pItem->pszText[iSubItem] 
						: _T( "" ), 
						WS_VISIBLE|WS_CHILD|( iSubItem ? ( WS_VSCROLL|ES_MULTILINE|ES_AUTOVSCROLL ) : ES_AUTOHSCROLL ),
				pList->labelEdit.rcCombo.left, pList->labelEdit.rcCombo.top,
				pList->labelEdit.rcCombo.right - pList->labelEdit.rcCombo.left,
				pList->labelEdit.rcCombo.bottom - pList->labelEdit.rcCombo.top,
				pList->hList, NULL, ghInst, NULL );
	if( !pList->labelEdit.hEdit )
		return NULL;
	SendMessage( pList->labelEdit.hEdit, WM_SETFONT, ( WPARAM )( pList->hFont ), 0 );
	SendMessage( pList->labelEdit.hEdit, EM_SETSEL, 0, ( LPARAM )-1 );
	SetUserData( pList->labelEdit.hEdit, pList );
	pList->labelEdit.dropDown.iItem = pItem->iListItem;
	pList->labelEdit.iItem = iItem;
	pList->labelEdit.iSubItem = iSubItem;
	pList->labelEdit.iTopIndex = ListView_GetTopIndex( pList->hList );
	pList->labelEdit.pItem = pItem;
	SetFocus( pList->labelEdit.hEdit );
	OldEditProc = ( WNDPROC )SetWindowLongPtr( pList->labelEdit.hEdit, GWL_WNDPROC, ( LONG_PTR )ProfileList_LabelEditProc );
	return pList->labelEdit.hEdit;
}

/**
 * name:	ProfileList_BeginLabelEdit
 * desc:	create an edit control to edit the label of the selected item
 * param:	hList	 - handle to listview control
 *			iItem	 - item index
 *			iSubItem - subitem ( column ) index
 * return:	handle to the edit control
 **/
static HWND ProfileList_BeginLabelEdit( HWND hList, INT iItem, INT iSubItem )
{
	return ProfileList_BeginLabelEdit( ( LPLISTCTRL )GetUserData( hList ), iItem, iSubItem );
}

/**
 * name:	ProfileList_GetInsertIndex
 * desc:	finds index to add the new item to and adds an devider if necessary
 * param:	hList	- handle to listcontrol to search for the index in
 *			pszList	- database settings string, that identifies this category
 * 
 * return:	zero based index for the new item or -1 on failure
 **/
static INT ProfileList_GetInsertIndex( HWND hList, LPTSTR pszGroup )
{
	LVFINDINFO	lfi;
	INT			iDevider, 
				iItem;
	
	// search for the devider to add the new item under
	lfi.flags = LVFI_STRING;
	lfi.psz = pszGroup;
	iDevider = SendMessage( hList, LVM_FINDITEM, ( WPARAM )-1, ( LPARAM )&lfi );
	
	// devider does not exist yet, add it!
	if( iDevider == -1 ) {
		LVITEM	lvi;

		lvi.mask = LVIF_PARAM|LVIF_TEXT;
		lvi.iSubItem = 0;
		lvi.iItem = 0xFFFF;
		lvi.lParam = NULL;
		lvi.pszText = ( LPTSTR )lfi.psz;
		if( ( iItem = ListView_InsertItem( hList, &lvi ) ) == -1 ) {
			return -1;
		}
		iItem++;
	}
	else {
		// search next devider to add new item just before
		lfi.flags = LVFI_PARAM;
		lfi.lParam = NULL;
		if( ( iItem = ListView_FindItem( hList, iDevider, &lfi ) ) == -1 )
			iItem = 0xFFFF;
	}
	return iItem;
}

/**
 * name:	ProfileList_AddNewItem
 * desc:	Ask's user for a type and adds new item to the list view
 * param:	pList	- pointer to the listview's data structure
 *			pszList	- database settings string, that identifies this category
 *
 * return:	TRUE or FALSE
 **/
static BOOLEAN ProfileList_AddNewItem( HWND hDlg, LPLISTCTRL pList, const PROFILEENTRY *pEntry )
{
	LPLCITEM pItem;
	LVITEM lvi;
	HANDLE hContact;

	if( PtrIsValid( pList ) && ( pItem = ( LPLCITEM )malloc( sizeof( LCITEM ) ) ) ) {
		PSGetContact( hDlg, hContact );
		pItem->nType = CTRL_LIST_ITEM;
		pItem->wFlags = hContact ? CTRLF_HASCUSTOM : 0;
		pItem->iListItem = 0;
		pItem->pszText[0] = NULL;
		pItem->pszText[1] = NULL;
		// get category list
		pEntry->GetList( ( WPARAM )&pItem->idstrListCount, ( LPARAM )&pItem->idstrList );

		lvi.mask = LVIF_PARAM|LVIF_STATE;
		lvi.stateMask = 0xFFFFFFFF;
		lvi.state = LVIS_FOCUSED|LVIS_SELECTED;
		lvi.iItem = ProfileList_GetInsertIndex( pList->hList, pEntry->szGroup );
		lvi.iSubItem = 0;
		lvi.lParam = ( LPARAM )pItem;
		if( ( lvi.iItem = ListView_InsertItem( pList->hList, &lvi ) ) >= 0 ) {
			ProfileList_BeginLabelEdit( pList, lvi.iItem, 0 );
			return TRUE;
		}
		free( pItem );
		MsgErr( hDlg, LPGENT( "Sorry, but there is a problem with adding a new item of type \"%s\"" ), pEntry->szGroup );
	}	
	return FALSE;
}

/**
 * name:	ProfileList_AddItemlistFromDB
 * desc:	reads an zero based indexed szList from the database of hContacts szModule and fills a hList
 * param:	hList		- HANDLE to the list to fill with two columns
 *			iItem		- index of new listviewitem
 *			iImage		- image to draw from a imagelist associated with the listview
 *			hContact	- handle to the contact, whose information are read
 *			szModule	- the module the information are stored in
 *			szCatFormat	- name of a database setting that holds the categorytext
 *			szValFormat - name of a database setting that holds the valuetext
 *			wFlags		- flags to set for a new allocated item
 *
 * return	number of added rows or -1 if listview's changed flag is set
 **/
static INT ProfileList_AddItemlistFromDB(
				LPLISTCTRL pList,
				INT &iItem,
				LPIDSTRLIST idList,
				UINT nList,
				HANDLE hContact,
				LPCSTR szModule,
				LPCSTR szCatFormat,
				LPCSTR szValFormat,
				WORD wFlags )
{
	DBVARIANT	dbvVal, dbvCat;
	LPLCITEM	pItem;
	LVITEM		lvi;
	UINT		i, j = 0;
	CHAR		szSetting[MAXSETTING];

	lvi.iSubItem = 0;
	lvi.mask = LVIF_PARAM;

	for( i = 0, lvi.iItem = iItem; ; i++ ) {
		// read the setting from db
		mir_snprintfA( szSetting, MAXSETTING, szValFormat, i );
		if( DB::Setting::GetTString( hContact, szModule, szSetting, &dbvVal ) ) break;
		if( dbvVal.type != DBVT_TCHAR ) continue;
		mir_snprintfA( szSetting, MAXSETTING, szCatFormat, i );
		DB::Setting::GetTString( hContact, szModule, szSetting, &dbvCat );
		// create the itemobject
		if( !( pItem = ( LPLCITEM )malloc( sizeof( LCITEM ) ) ) ) {
			DB::Variant::Free( &dbvCat );
			DB::Variant::Free( &dbvVal );
			break;
		}
		// fill item struct
		pItem->nType = CTRL_LIST_ITEM;
		pItem->idstrList = idList;
		pItem->idstrListCount = nList;
		pItem->iListItem = 0;
		pItem->pszText[0] = NULL;
		pItem->pszText[1] = dbvVal.ptszVal;
		pItem->wFlags = wFlags;
		lvi.lParam = ( LPARAM )pItem;
		
		// get id-str-list-item for the category string
		if( idList != NULL ) {
			for( j = 0; j < nList; j++ ) {
				switch( dbvCat.type ) {
					case DBVT_BYTE:
						if( dbvCat.bVal != ( BYTE )idList[j].nID )
							continue;
						break;
					case DBVT_WORD:
						if( dbvCat.wVal != ( WORD )idList[j].nID )
							continue;
						break;
					case DBVT_DWORD:
						if( dbvCat.dVal != ( DWORD )idList[j].nID )
							continue;
						break;
					case DBVT_TCHAR:			
						if( _tcscmp( dbvCat.ptszVal, idList[j].szText ) ) 
							continue;
						break;
				}
				pItem->iListItem = j;
				break;
			}
		}
		// item not found in the predefined category list?
		if( ( idList == NULL || j == nList ) && dbvCat.type == DBVT_TCHAR ) {
			pItem->pszText[0] = dbvCat.ptszVal;
		}
		if( ( lvi.iItem = ListView_InsertItem( pList->hList, &lvi ) ) < 0 ) {
			free( pItem );
			DB::Variant::Free( &dbvCat );
			DB::Variant::Free( &dbvVal );
			break;
		}
		lvi.iItem++;
		dbvCat.type = dbvVal.type = DBVT_DELETED;
	}
	iItem = lvi.iItem;
	return i;
}

/**
 * name:	ProfileList_DropdownProc
 * desc:	procedure to catch messages for a listbox control for my own combobox
 * param:	hwnd	- handle to the listview control's window
 *			msg		- message sent to the control
 *			wParam	- message specific parameter
 *			lParam	- message specific parameter
 * return:	message specific
 **/
static LRESULT CALLBACK ProfileList_DropdownProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) 
{
	LPLISTCTRL pList;

	switch( msg ) {
		case WM_KEYDOWN:
			switch( wParam ) {
				case VK_ESCAPE:
					SetFocus( GetParent( hwnd ) );
					return 0;

				case VK_RETURN:
				case VK_F4:
				{
					LPIDSTRLIST pItem;

					if( !PtrIsValid( pList = ( LPLISTCTRL )GetUserData( hwnd ) ) ) return CB_ERR;
					pList->labelEdit.dropDown.iItem = ListBox_GetCurSel( hwnd );
					if( pList->labelEdit.dropDown.iItem >= 0 && PtrIsValid( pItem = ( LPIDSTRLIST )ListBox_GetItemData( hwnd, pList->labelEdit.dropDown.iItem ) ) )
						SetWindowText( pList->labelEdit.hEdit, pItem->ptszTranslated );
					else
						pList->labelEdit.dropDown.iItem = -1;
					SetFocus( pList->labelEdit.hEdit );
					return 0;
				}
			}
			break;
		case WM_LBUTTONUP:
		{
			POINT pt;
			LPIDSTRLIST pItem;

			if( !PtrIsValid( pList = ( LPLISTCTRL )GetUserData( hwnd ) ) ) return CB_ERR;
			CallWindowProc( OldDropdownProc, hwnd, msg, wParam, lParam );

            pt.x = ( short )LOWORD( lParam );
            pt.y = ( short )HIWORD( lParam );
            ClientToScreen( hwnd, &pt );

			if( SendMessage( hwnd, WM_NCHITTEST, 0, MAKELPARAM( pt.x, pt.y ) ) == HTVSCROLL ) 
				return CB_ERR;
            
			pList->labelEdit.dropDown.iItem = SendMessage( hwnd, LB_GETCURSEL, 0, 0 );
			
			if( pList->labelEdit.dropDown.iItem >= 0 && PtrIsValid( pItem = ( LPIDSTRLIST )ListBox_GetItemData( hwnd, pList->labelEdit.dropDown.iItem ) ) ) {	
				SetWindowText( pList->labelEdit.hEdit, pItem->ptszTranslated );
			}
			else
				pList->labelEdit.dropDown.iItem = -1;

			ProfileList_EndLabelEdit( pList->hList, TRUE );
			return 0;
		}
		case WM_KILLFOCUS:
			if( PtrIsValid( pList = ( LPLISTCTRL )GetUserData( hwnd ) ) ) {
				if( GetFocus() == pList->labelEdit.hEdit ) {
					ShowWindow( hwnd, SW_HIDE );
					return 0;
				}
				ProfileList_EndLabelEdit( pList, FALSE );
			}
			return 0;
	}
	return CallWindowProc( OldDropdownProc, hwnd, msg, wParam, lParam );
}

/**
 * name:	ProfileList_LabelEditProc
 * desc:	procedure to catch messages for an edit control for my own combobox
 * param:	hwnd	- handle to the listview control's window
 *			msg		- message sent to the control
 *			wParam	- message specific parameter
 *			lParam	- message specific parameter
 * return:	message specific
 **/
static LRESULT CALLBACK ProfileList_LabelEditProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
	LPLISTCTRL pList;

	switch( msg ) {
		case WM_KEYDOWN:
			switch( wParam ) {
				case VK_ESCAPE:
					if( PtrIsValid( pList = ( LPLISTCTRL )GetUserData( hwnd ) ) )
						ProfileList_EndLabelEdit( pList, FALSE );
					return 0;
				case VK_F3:
					if( PtrIsValid( pList = ( LPLISTCTRL )GetUserData( hwnd ) ) && pList->labelEdit.hBtn )
						SendMessage( pList->hList, WM_COMMAND, MAKEWPARAM( BTN_EDIT, BN_CLICKED ), ( LPARAM )pList->labelEdit.hBtn );
					return 0;
				case VK_RETURN:
				{
					BOOLEAN bEditNext;
					INT iItem;
		
					if( GetWindowLongPtr( hwnd, GWL_STYLE ) & ES_WANTRETURN && !( GetKeyState( VK_CONTROL ) & 0x8000 ) )
						break;
					if( PtrIsValid( pList = ( LPLISTCTRL )GetUserData( hwnd ) ) ) {
						bEditNext = !pList->labelEdit.iSubItem && !ProfileList_GetItemText( pList->hList, pList->labelEdit.iItem, 1, NULL, NULL );
						iItem = pList->labelEdit.iItem;
						ProfileList_EndLabelEdit( pList->hList, TRUE );
						if( bEditNext ) ProfileList_BeginLabelEdit( pList->hList, pList->labelEdit.iItem, 1 );
					}
					return 0;
				}
				case VK_TAB:
					if( PtrIsValid( pList = ( LPLISTCTRL )GetUserData( hwnd ) ) ) {
						LVITEM lvi;

						lvi.mask = LVIF_STATE;
						lvi.stateMask = LVIS_FOCUSED|LVIS_SELECTED;
						lvi.iItem = pList->labelEdit.iItem;

						if( wParam == VK_TAB && !pList->labelEdit.iSubItem ) {
							lvi.iSubItem = 1;
							lvi.state = LVIS_FOCUSED|LVIS_SELECTED;
							ProfileList_EndLabelEdit( pList->hList, TRUE );
						}
						else {
							UINT iSubItem = ( wParam == VK_TAB ) ? 0 : pList->labelEdit.iSubItem;

							lvi.iSubItem = 0;
							lvi.state = 0;

							ProfileList_EndLabelEdit( pList, TRUE );

							// unselect current list item
							if( !ListView_SetItem( pList->hList, &lvi ) )
								return 0;

							// search for next valid list item ( skip deviders )
							lvi.iSubItem = iSubItem;
							lvi.mask = LVIF_PARAM;
							do {
								if( wParam == VK_UP ) 
									lvi.iItem--;
								else
									lvi.iItem++;

								if( lvi.iItem == -1 || !ListView_GetItem( pList->hList, &lvi ) ) {
									return 0;
								}
							} while( !lvi.lParam );

							// select new list item
							lvi.mask = LVIF_STATE;
							lvi.state = LVIS_FOCUSED|LVIS_SELECTED;
							if( !ListView_SetItem( pList->hList, &lvi ) ){
								return 0;
							}
						}
						ProfileList_BeginLabelEdit( pList->hList, lvi.iItem, lvi.iSubItem );
						return 0;
					}
					return 1;
			}
			break;
		case WM_GETDLGCODE:
			return DLGC_WANTALLKEYS | CallWindowProc( OldEditProc, hwnd, msg, wParam, lParam );
		case WM_KILLFOCUS:
		{
			HWND hwndFocus = GetFocus();

			if( PtrIsValid( pList = ( LPLISTCTRL )GetUserData( hwnd ) ) && 
				hwndFocus != pList->labelEdit.dropDown.hDrop && 
				hwndFocus != pList->labelEdit.hEdit &&
				hwndFocus != pList->labelEdit.hBtn )
				ProfileList_EndLabelEdit( pList, hwndFocus == pList->hList );
			return 0;
		}
	}
	return CallWindowProc( OldEditProc, hwnd, msg, wParam, lParam );
}

/**
 * name:	ListSubclassProc
 * desc:	procedure to catch messages for a listview control, to handle tooltips
 * param:	hwnd	- handle to the listview control's window
 *			msg		- message sent to the control
 *			wParam	- message specific parameter
 *			lParam	- message specific parameter
 * return:	message specific
 **/
static LRESULT CALLBACK ProfileList_SubclassProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) 
{
	LPLISTCTRL pList;

	switch( msg ) {
		case WM_KEYDOWN:
		{
			INT nCurSel, newSel;
			LVITEM lvi;

			switch( wParam ) {
				case VK_F2:
					nCurSel = ListView_GetSelectionMark( hwnd );
					if( nCurSel == -1 ) break;
					ProfileList_BeginLabelEdit( hwnd, nCurSel, 0 );
					return 0;
				case VK_F3:
					nCurSel = ListView_GetSelectionMark( hwnd );
					if( nCurSel == -1 ) break;
					ProfileList_BeginLabelEdit( hwnd, nCurSel, 1 );
					return 0;
				case VK_UP:
				case VK_DOWN:
					lvi.iItem = nCurSel = ListView_GetSelectionMark( hwnd );
					lvi.iSubItem = 0;

					// find next valid item to select
					lvi.mask = LVIF_PARAM;
					do {
						if( wParam == VK_UP ) lvi.iItem--;
						else lvi.iItem++;
						if( lvi.iItem == -1 || !ListView_GetItem( hwnd, &lvi ) ) {
							return 0;
						}
					} while( !lvi.lParam );

					ListView_EnsureVisible( hwnd, lvi.iItem, FALSE );
					newSel = lvi.iItem;
					lvi.iItem = nCurSel;
					lvi.mask = LVIF_STATE;
					lvi.stateMask = LVIS_FOCUSED|LVIS_SELECTED;
					lvi.state = 0;
					ListView_SetItem( hwnd, &lvi );
					lvi.iItem = newSel;
					lvi.state = LVIS_FOCUSED|LVIS_SELECTED;
					ListView_SetItem( hwnd, &lvi );
					ListView_SetSelectionMark( hwnd, lvi.iItem );
					return 0;
			}
			break;
		}
		case WM_MOUSEMOVE:
			if( PtrIsValid( pList = ( LPLISTCTRL )GetUserData( hwnd ) ) ) {
				HDC hDC;
				RECT rchWnd, rcItem;
				SIZE textSize;
				LVHITTESTINFO hi;
				TOOLINFO ti;
				BOOLEAN bReposition;
				LPLCITEM pItem;
							
				hi.pt.x = GET_X_LPARAM( lParam );
				hi.pt.y = GET_Y_LPARAM( lParam );
				ListView_SubItemHitTest( hwnd, &hi );

				// show tip only if pointer is over an item
				if( pList->iHotItem != hi.iItem || pList->iHotSubItem != hi.iSubItem ) {
					bReposition = pList->iHotItem != -1 || pList->iHotSubItem != -1;
					pList->iHotItem = hi.iItem;
					pList->iHotSubItem = hi.iSubItem;
					
					if( ( hi.flags & LVHT_ONITEMLABEL ) && PtrIsValid( pItem = ProfileList_GetItemData( hwnd, hi.iItem ) ) ) {
						GetWindowRect( hwnd, &rchWnd );
						ListView_GetSubItemRect( hwnd, hi.iItem, hi.iSubItem, LVIR_BOUNDS, &rcItem );
						// calculate size of text on the screen
						if( ( hDC = GetDC( GetParent( hwnd ) ) ) ) {
							SelectObject( hDC, ( HFONT )SendMessage( GetParent( hwnd ), WM_GETFONT, NULL, NULL ) );
							GetTextExtentPoint32( hDC, pItem->pszText[hi.iSubItem], lstrlen( pItem->pszText[hi.iSubItem] ), &textSize );
							ReleaseDC( GetParent( hwnd ), hDC );
						}
						// show tip only for text that is larger than te listview can display
						if( textSize.cx > rchWnd.right - rchWnd.left || textSize.cx > rcItem.right - rcItem.left ) {
							ZeroMemory( &ti, sizeof( TOOLINFO ) );
							ti.cbSize = sizeof( TOOLINFO );
							ti.uFlags = TTF_IDISHWND|TTF_SUBCLASS|TTF_TRANSPARENT;
							ti.hinst = ghInst;
							ti.hwnd = hwnd;
							ti.uId = ( UINT )hwnd;
							ti.lpszText = pItem->pszText[hi.iSubItem];
							ti.rect = rcItem;
							SendMessage( pList->hTip, TTM_SETMAXTIPWIDTH, 0, 300 );
							SendMessage( pList->hTip, TTM_SETTOOLINFO, NULL, ( LPARAM )&ti );
							if( pList->iHotSubItem > 0 ) {
								SendMessage( pList->hTip, TTM_SETTITLE, 1, ( LPARAM )
									( ( pItem->idstrList && pItem->iListItem > 0 && pItem->iListItem < pItem->idstrListCount )
									? pItem->idstrList[pItem->iListItem].ptszTranslated
										: ( pItem->pszText[0] && *pItem->pszText[0] )
											? pItem->pszText[0]
											: TranslateT( "<empty>") )
									 );
								InvalidateRect( pList->hTip, NULL, TRUE );
							}
							else
								SendMessage( pList->hTip, TTM_SETTITLE, 0, ( LPARAM )"" );
							SendMessage( pList->hTip, TTM_ACTIVATE, TRUE, ( LPARAM )&ti );
							pList->ptTip.x = rchWnd.left + GET_X_LPARAM( lParam ) - 16;
							pList->ptTip.y = rchWnd.top + rcItem.top;
							// no TTN_SHOW is called if bReposition is TRUE, so repose here!
							if( bReposition ) {
								RECT rcTip;
								GetClientRect( pList->hTip, &rcTip );
								SetWindowPos( pList->hTip, hwnd, pList->ptTip.x, pList->ptTip.y - rcTip.bottom, 0, 0, SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE );
							}
							pList->wFlags |= LIF_TIPVISIBLE;
							return 0;
						}
					}
					if( pList->wFlags & LIF_TIPVISIBLE ) {
						SendMessage( pList->hTip, TTM_ACTIVATE, FALSE, ( LPARAM )&ti );
						pList->wFlags &= ~LIF_TIPVISIBLE;
					}
				}
			}
			return 0;

			// begin label edit
			case WM_LBUTTONDBLCLK:
			{
				LVHITTESTINFO hi;

				hi.pt.x = GET_X_LPARAM( lParam );
				hi.pt.y = GET_Y_LPARAM( lParam );
				if( ListView_SubItemHitTest( hwnd, &hi ) ) {
					ProfileList_BeginLabelEdit( hwnd, hi.iItem, hi.iSubItem );
				}
				return TRUE;
			}
			
		case WM_NOTIFY:
			if( !PtrIsValid( pList = ( LPLISTCTRL )GetUserData( hwnd ) ) )
				break;
		
			// ensure position of tooltip is on the topline of the item
			if( (( LPNMHDR )lParam )->hwndFrom == pList->hTip ) {
				RECT rcTip;
				GetClientRect( pList->hTip, &rcTip );
				
				switch( (( LPNMHDR )lParam )->code ) {
					case TTN_SHOW:
						SetWindowPos( pList->hTip, hwnd, pList->ptTip.x, pList->ptTip.y - rcTip.bottom, 0, 0, SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE );
						return TRUE;
				}
			}
			break;
		case WM_COMMAND:
		{
			switch( LOWORD( wParam ) ) {

				// show dropdown menu for category list
				case BTN_EDIT:
				{
					INT i;
					TCHAR szEdit[MAX_PATH];

					if( !PtrIsValid( pList = ( LPLISTCTRL )GetUserData( hwnd ) ) ) break;
					GetWindowText( pList->labelEdit.hEdit, szEdit, MAX_PATH );

					// need to create the dropdown list?
					if( pList->labelEdit.dropDown.hDrop == NULL ) {
						const INT listHeight = 120;
						RECT rc, rcList;
						INT add;

						// dropdown rect
						GetClientRect( pList->hList, &rcList );
						rc.left = pList->labelEdit.rcCombo.left;
						rc.right = pList->labelEdit.rcCombo.right + pList->labelEdit.rcCombo.bottom - pList->labelEdit.rcCombo.top; 
						
						if( rcList.bottom < pList->labelEdit.rcCombo.bottom + listHeight ) {
							rc.bottom = pList->labelEdit.rcCombo.bottom - 7; // don't ask me why!
							rc.top = rc.bottom - listHeight;
						}
						else {
							rc.top = pList->labelEdit.rcCombo.bottom;
							rc.bottom = rc.top + listHeight;
						}

						pList->labelEdit.dropDown.hDrop = CreateWindowEx( 0,
									_T( "LISTBOX" ), NULL, WS_CHILD|WS_BORDER|WS_VSCROLL|LBS_COMBOBOX|LBS_HASSTRINGS,
									rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
									hwnd, NULL, ghInst, NULL );
						if( !pList->labelEdit.dropDown.hDrop ) return FALSE;
						SetUserData( pList->labelEdit.dropDown.hDrop, pList );
						OldDropdownProc = ( WNDPROC )SetWindowLongPtr( pList->labelEdit.dropDown.hDrop, GWL_WNDPROC, ( LONG_PTR )ProfileList_DropdownProc );
						SetWindowLongPtr( pList->labelEdit.dropDown.hDrop, GWL_ID, LIST_DROPDOWN );
						SendMessage( pList->labelEdit.dropDown.hDrop, WM_SETFONT, ( WPARAM )SendMessage( GetParent( pList->hList ), WM_GETFONT, 0, 0 ), 0 );
						
						// add items
						for( i = 0; i < pList->labelEdit.pItem->idstrListCount; i++ ) {
							add = ListBox_AddString( pList->labelEdit.dropDown.hDrop, pList->labelEdit.pItem->idstrList[i].ptszTranslated );
							ListBox_SetItemData( pList->labelEdit.dropDown.hDrop, add, pList->labelEdit.pItem->idstrList + i );
							if( !_tcscmp( szEdit, pList->labelEdit.pItem->idstrList[i].ptszTranslated ) )
								ListBox_SetCurSel( pList->labelEdit.dropDown.hDrop, add );
						}
					}
					else {
						LPIDSTRLIST lpidList;

						i = 0;
						while( PtrIsValid( lpidList = ( LPIDSTRLIST )ListBox_GetItemData( pList->labelEdit.dropDown.hDrop, i ) ) ) {
							if( !_tcscmp( szEdit, lpidList->ptszTranslated ) ) {
								ListBox_SetCurSel( pList->labelEdit.dropDown.hDrop, i );
								break;
							}
							i++;
						}
						if( i == pList->labelEdit.pItem->idstrListCount ) 
							ListBox_SetCurSel( pList->labelEdit.dropDown.hDrop, -1 );
					}
					if( IsWindowVisible( pList->labelEdit.dropDown.hDrop ) ) {
						SetFocus( pList->labelEdit.hEdit );
					}
					else {
						ShowWindow( pList->labelEdit.dropDown.hDrop, SW_SHOW );
						SetFocus( pList->labelEdit.dropDown.hDrop );
					}
					break;
				}
			}
			break;
		}
		case WM_MOUSEWHEEL:
		case WM_VSCROLL:
		case WM_HSCROLL:
		{
			if( PtrIsValid( pList = ( LPLISTCTRL )GetUserData( hwnd ) ) )
				ProfileList_EndLabelEdit( pList, FALSE );
			break;
		}

		case WM_KILLFOCUS:
		{
			HWND hwndFocus = GetFocus();

			if( PtrIsValid( pList = ( LPLISTCTRL )GetUserData( hwnd ) ) &&
				pList->labelEdit.hEdit != hwndFocus && 
				pList->labelEdit.dropDown.hDrop != hwndFocus && 
				pList->labelEdit.hBtn != hwndFocus )
				ProfileList_EndLabelEdit( pList, FALSE );
			break;
		}

		case WM_DESTROY:
			if( PtrIsValid( pList = ( LPLISTCTRL )GetUserData( hwnd ) ) ) {
				HFONT hFont;

				ProfileList_EndLabelEdit( pList, FALSE );
				ProfileList_Clear( hwnd );
				if( PtrIsValid( hFont = ( HFONT )SendMessage( hwnd, WM_GETFONT, 0, 0 ) ) && hFont != pList->hFont )
					DeleteObject( hFont );
				DestroyWindow( pList->hTip );
				free( pList );
			}
			break;
	}
	return CallWindowProc( OldListViewProc, hwnd, msg, wParam, lParam );
}

/**
 * name:	DlgProcPspAbout()
 * desc:	dialog procedure
 *
 * return:	0 or 1
 **/
INT CALLBACK DlgProc_pspContactProfile( HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	HWND hList = GetDlgItem( hDlg, LIST_PROFILE );
	LPLISTCTRL pList;

	switch( uMsg ) {
		case WM_INITDIALOG:
		{
			LVCOLUMN lvc;
			RECT rc;
			LOGFONT lf;
			HFONT hFont;
			TOOLINFO ti;

			if( !hList || !( pList = ( LPLISTCTRL )malloc( sizeof( LISTCTRL ) ) ) ) 
				return FALSE;
			ZeroMemory( pList, sizeof( LISTCTRL ) );

			TranslateDialogDefault( hDlg );
			Ctrl_InitTextColours();

			// init info structure
			pList->hList = hList;
			pList->nType = CTRL_LIST_PROFILE;
			ZeroMemory( &pList->labelEdit, sizeof( pList->labelEdit ) );
			SetUserData( hList, pList );

			// set new window procedure
			OldListViewProc = ( WNDPROC )SetWindowLongPtr( hList, GWL_WNDPROC, ( LONG_PTR )&ProfileList_SubclassProc );

			// insert columns into the listboxes					
			ListView_SetExtendedListViewStyle( hList, LVS_EX_FULLROWSELECT );

			// set listfont
			pList->hFont = ( HFONT )SendMessage( hList, WM_GETFONT, 0, 0 );
			pList->wFlags |= LVF_EDITLABEL;
			GetObject( pList->hFont, sizeof( lf ), &lf );
			lf.lfHeight -= 6;
			hFont = CreateFontIndirect( &lf );
			SendMessage( hList, WM_SETFONT, ( WPARAM )hFont, 0 );

			GetClientRect( hList, &rc );
			rc.right -= GetSystemMetrics( SM_CXVSCROLL );

			// initiate the tooltips
			pList->hTip = CreateWindowEx( WS_EX_TOPMOST,	TOOLTIPS_CLASS, NULL,
				WS_POPUP|TTS_BALLOON|TTS_NOPREFIX|TTS_ALWAYSTIP,
				CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
				hList, NULL, ghInst, NULL );
			if( pList->hTip ) {
				SetWindowPos( pList->hTip, HWND_TOPMOST,
					CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
					SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE );

				ZeroMemory( &ti, sizeof( TOOLINFO ) );
				ti.cbSize = sizeof( TOOLINFO );
				ti.uFlags = TTF_IDISHWND|TTF_SUBCLASS|TTF_TRANSPARENT;
				ti.hinst = ghInst;
				ti.hwnd = hList;
				ti.uId = ( UINT )hList;
				SendMessage( pList->hTip, TTM_ADDTOOL, NULL, ( LPARAM )&ti );
				SendMessage( pList->hTip, TTM_ACTIVATE, FALSE, ( LPARAM )&ti );
			}		

			// insert columns into the listboxes					
			lvc.mask = LVCF_WIDTH;
			lvc.cx = rc.right / 8 * 3;
			ListView_InsertColumn( hList, 0, &lvc );
			lvc.cx = rc.right / 8 * 5;
			ListView_InsertColumn( hList, 1, &lvc );
			return TRUE;
		}

		case WM_NOTIFY:
			switch ( (( LPNMHDR )lParam )->idFrom ) {
				case 0:
				{
					HANDLE hContact = ( HANDLE )( ( LPPSHNOTIFY )lParam )->lParam;
					LPCSTR szBaseProto;
					
					if( !PtrIsValid( pList = ( LPLISTCTRL )GetUserData( hList ) ) ) break;

					switch ( (( LPNMHDR )lParam )->code ) {
						// some account data may have changed so reread database
						case PSN_INFOCHANGED:
						{
							BYTE msgResult = 0;
							LPIDSTRLIST idList;
							UINT nList;
							BYTE i;
							INT iItem = 0,
								iGrp = 0,
								numProtoItems,
								numUserItems;

							if( !( pList->wFlags & CTRLF_CHANGED ) && PSGetBaseProto( hDlg, szBaseProto ) && *szBaseProto != 0 ) {
								ProfileList_Clear( hList );

								// insert the past information
								for( i = 0; i < 3; i++ ) {
									pFmt[i].GetList( ( WPARAM )&nList, ( LPARAM )&idList );
									if( ( numProtoItems = ProfileList_AddItemlistFromDB( pList, iItem, idList, nList, hContact, szBaseProto, pFmt[i].szCatFmt, pFmt[i].szValFmt, CTRLF_HASPROTO ) ) < 0 )
										return FALSE;

									// scan all basic protocols for the subcontacts
									if( DB::Module::IsMetaAndScan( szBaseProto ) ) {
										INT iDefault = CallService( MS_MC_GETDEFAULTCONTACTNUM, ( WPARAM )hContact, NULL );
										HANDLE hSubContact, hDefContact;
										LPCSTR pszSubBaseProto;
										INT j, numSubs;
										
										if( ( hDefContact = ( HANDLE )CallService( MS_MC_GETSUBCONTACT, ( WPARAM )hContact, iDefault ) ) &&
										   ( pszSubBaseProto = DB::Contact::Proto( hDefContact ) ) )
										{
											if( ( numProtoItems += ProfileList_AddItemlistFromDB( pList, iItem, idList, nList, hDefContact, pszSubBaseProto, pFmt[i].szCatFmt, pFmt[i].szValFmt, CTRLF_HASMETA|CTRLF_HASPROTO ) ) < 0 )
												return FALSE;

											// copy the missing settings from the other subcontacts
											numSubs = CallService( MS_MC_GETNUMCONTACTS, ( WPARAM )hContact, NULL );
											for( j = 0; j < numSubs; j++ ) {
												if( j == iDefault ) continue;
												if( !( hSubContact = ( HANDLE )CallService( MS_MC_GETSUBCONTACT, ( WPARAM )hContact, j ) ) ) continue;
												if( !( pszSubBaseProto = DB::Contact::Proto( hSubContact ) ) ) continue;
												if( ( numProtoItems += ProfileList_AddItemlistFromDB( pList, iItem, idList, nList, hSubContact, pszSubBaseProto, pFmt[i].szCatFmt, pFmt[i].szValFmt, CTRLF_HASMETA|CTRLF_HASPROTO ) ) < 0 )
													return FALSE;
												//if( ( numUserItems += ProfileList_AddItemlistFromDB( pList, iItem, idList, nList, hSubContact, USERINFO, pFmt[i].szCatFmt, pFmt[i].szValFmt, CTRLF_HASMETA|CTRLF_HASPROTO ) ) < 0 )
												//	return FALSE;
											}
										}
									}
									if( ( numUserItems = ProfileList_AddItemlistFromDB( pList, iItem, idList, nList, hContact, USERINFO, pFmt[i].szCatFmt, pFmt[i].szValFmt, CTRLF_HASCUSTOM ) ) < 0 )
										return FALSE;
									if( numUserItems || numProtoItems ) {
										msgResult = PSP_CHANGED;
										ProfileList_AddGroup( hList, pFmt[i].szGroup, iGrp );
										iGrp = ++iItem;
									}
								}
							}
							SetWindowLongPtr( hDlg, DWL_MSGRESULT, msgResult );
							break;
						}
						// user swiches to another propertysheetpage
						case PSN_KILLACTIVE:
							ProfileList_EndLabelEdit( hList, TRUE );
							break;
						// user selected to apply settings to the database
						case PSN_APPLY:
							if( pList->wFlags & CTRLF_CHANGED ) {
								BYTE iFmt = -1;
								INT iItem;
								LVITEM lvi;
								TCHAR szGroup[MAX_PATH];
								CHAR szSetting[MAXSETTING];
								LPLCITEM pItem;
								LPSTR szModule = USERINFO;

								if( !hContact ) PSGetBaseProto( hDlg, szModule );

								*szGroup = 0;
								lvi.mask = LVIF_TEXT|LVIF_PARAM;
								lvi.pszText = szGroup;
								lvi.cchTextMax = MAX_PATH;

								for( iItem = lvi.iItem = lvi.iSubItem = 0; ListView_GetItem( hList, &lvi ); lvi.iItem++ ) {
									if( !PtrIsValid( pItem = ( LPLCITEM )lvi.lParam ) ) {
										// delete reluctant items
										if( iFmt >= 0 && iFmt < SIZEOF( pFmt ) ) {
											DB::Setting::DeleteArray( hContact, szModule, pFmt[iFmt].szCatFmt, iItem );
											DB::Setting::DeleteArray( hContact, szModule, pFmt[iFmt].szValFmt, iItem );
										}
										// find information about the group
										for( iFmt = 0; iFmt < SIZEOF( pFmt ); iFmt++ ) {
											if( !_tcscmp( szGroup, pFmt[iFmt].szGroup ) ) {
												break;
											}
										}
										// indicate, no group was found. should not happen!!
										if( iFmt == SIZEOF( pFmt ) ) {
											*szGroup = 0;
											iFmt = -1;
										}
										iItem = 0;
									}
									else
									if( iFmt >= 0 && iFmt < SIZEOF( pFmt ) ) {
										// save value
										if( !pItem->pszText[1] || !*pItem->pszText[1] )
											continue;
										if( !( pItem->wFlags & ( CTRLF_HASPROTO|CTRLF_HASMETA ) ) ) {
											mir_snprintfA( szSetting, MAXSETTING, pFmt[iFmt].szValFmt, iItem );
											DB::Setting::WriteTString( hContact, szModule, szSetting, pItem->pszText[1] );
											// save category
											mir_snprintfA( szSetting, MAXSETTING, pFmt[iFmt].szCatFmt, iItem );
											if( pItem->idstrList && pItem->iListItem > 0 && pItem->iListItem < pItem->idstrListCount )
												DB::Setting::WriteTString( hContact, szModule, szSetting, ( LPTSTR )pItem->idstrList[pItem->iListItem].szText );
											else 
											if( pItem->pszText[0] && *pItem->pszText[0] )
												DB::Setting::WriteTString( hContact, szModule, szSetting, ( LPTSTR )pItem->pszText[0] );
											else									
												DB::Setting::Delete( hContact, szModule, szSetting );
											// redraw the item if required
											if( pItem->wFlags & CTRLF_CHANGED ) {
												pItem->wFlags &= ~CTRLF_CHANGED;
												ListView_RedrawItems( hList, lvi.iItem, lvi.iItem );
											}
											iItem++;
										}
									}
								}
								// delete reluctant items
								if( iFmt >= 0 && iFmt < SIZEOF( pFmt ) ) {
									DB::Setting::DeleteArray( hContact, szModule, pFmt[iFmt].szCatFmt, iItem );
									DB::Setting::DeleteArray( hContact, szModule, pFmt[iFmt].szValFmt, iItem );
								}

								pList->wFlags &= ~CTRLF_CHANGED;
							}
							break;
					}
					break;
				}

				//
				// handle notification messages from the list control
				//
				case LIST_PROFILE:
				{
					LPLISTCTRL pList = ( LPLISTCTRL )GetUserData( (( LPNMHDR )lParam )->hwndFrom );

					switch( (( LPNMHDR )lParam )->code ) {
						case NM_RCLICK:
						{
							HMENU hMenu = CreatePopupMenu();
							MENUITEMINFO mii;
							HANDLE hContact;
							LVHITTESTINFO hi;
							LPLCITEM pItem;
							POINT pt;
							
							if( !hMenu ) return 1;
							PSGetContact( hDlg, hContact );
							GetCursorPos( &pt );
							hi.pt = pt;
							ScreenToClient( (( LPNMHDR )lParam )->hwndFrom, &hi.pt );
							ListView_SubItemHitTest( (( LPNMHDR )lParam )->hwndFrom, &hi );
							pItem = ProfileList_GetItemData( (( LPNMHDR )lParam )->hwndFrom, hi.iItem );

							// insert menuitems
							ZeroMemory( &mii, sizeof( MENUITEMINFO ) );
							mii.cbSize = sizeof( MENUITEMINFO );
							mii.fMask = MIIM_ID|MIIM_STRING;
							// insert "Add" Menuitem
							mii.wID = BTN_ADD_intEREST;
							mii.dwTypeData = TranslateT( "Add Interest" );
							InsertMenuItem( hMenu, 0, TRUE, &mii );
							mii.wID = BTN_ADD_AFFLIATION;
							mii.dwTypeData = TranslateT( "Add Affliation" );
							InsertMenuItem( hMenu, 1, TRUE, &mii );
							mii.wID = BTN_ADD_PAST;
							mii.dwTypeData = TranslateT( "Add Past" );
							InsertMenuItem( hMenu, 2, TRUE, &mii );

							if( hi.iItem != -1 && PtrIsValid( pItem ) && !( hContact && ( pItem->wFlags & CTRLF_HASPROTO ) ) ) {
								// insert separator
								mii.fMask = MIIM_FTYPE;
								mii.fType = MFT_SEPARATOR;
								InsertMenuItem( hMenu, 3, TRUE, &mii );
								// insert "Delete" Menuitem
								mii.fMask = MIIM_ID|MIIM_STRING;
								mii.wID = BTN_EDIT_CAT;
								mii.dwTypeData = TranslateT( "Edit Category" );
								InsertMenuItem( hMenu, 4, TRUE, &mii );
								mii.wID = BTN_EDIT_VAL;
								mii.dwTypeData = TranslateT( "Edit Value" );
								InsertMenuItem( hMenu, 5, TRUE, &mii );
								mii.fMask = MIIM_FTYPE;
								mii.fType = MFT_SEPARATOR;
								InsertMenuItem( hMenu, 6, TRUE, &mii );
								// insert "Delete" Menuitem
								mii.fMask = MIIM_ID|MIIM_STRING;
								mii.wID = BTN_DEL;
								mii.dwTypeData = TranslateT( "Delete" );
								InsertMenuItem( hMenu, 7, TRUE, &mii );
							}
							TrackPopupMenu( hMenu, 0, pt.x, pt.y, 0, hDlg, 0 );
							DestroyMenu( hMenu );
							return 0;
						}
						/*case LVN_BEGINSCROLL:
							SetFocus( (( LPNMHDR )lParam )->hwndFrom );
							break;
							*/
						case LVN_GETDISPINFO:
							if( pList->labelEdit.iTopIndex != ListView_GetTopIndex( hList ) )
								ProfileList_EndLabelEdit( (( LPNMHDR )lParam )->hwndFrom, FALSE );
							break;
						case NM_CUSTOMDRAW:
						{
							LPNMLVCUSTOMDRAW cd = ( LPNMLVCUSTOMDRAW )lParam;
							LPLCITEM pItem = ( LPLCITEM )cd->nmcd.lItemlParam;
							RECT rc;

							switch( cd->nmcd.dwDrawStage ) {
								case CDDS_PREPAINT:
									SetWindowLongPtr( hDlg, DWL_MSGRESULT, CDRF_NOTIFYITEMDRAW );
									return TRUE;
								
								case CDDS_ITEMPREPAINT:
									ListView_GetItemRect( cd->nmcd.hdr.hwndFrom, cd->nmcd.dwItemSpec, &rc, LVIR_BOUNDS );
									if( !PtrIsValid( pItem ) ) {
										HFONT hBold, hFont;
										TCHAR szText[MAX_PATH];
										
										PSGetBoldFont( hDlg, hBold );
										hFont = ( HFONT )SelectObject( cd->nmcd.hdc, hBold );
										SetTextColor( cd->nmcd.hdc, GetSysColor( COLOR_3DSHADOW ) );
										ProfileList_GetItemText( cd->nmcd.hdr.hwndFrom, cd->nmcd.dwItemSpec, 0, szText, MAX_PATH );
										rc.left += 6;
										DrawText( cd->nmcd.hdc, TranslateTS( szText ), -1, &rc, DT_NOCLIP|DT_NOPREFIX|DT_SINGLELINE|DT_VCENTER );

										rc.bottom -= 2;
										rc.top = rc.bottom - 1;
										rc.left -= 6;
										DrawEdge( cd->nmcd.hdc, &rc, BDR_SUNKENOUTER, BF_RECT );

										SelectObject( cd->nmcd.hdc, hFont );
										SetWindowLongPtr( hDlg, DWL_MSGRESULT, CDRF_SKIPDEFAULT );
										return TRUE;
									}
									// draw selected item
									if( ( cd->nmcd.uItemState & CDIS_SELECTED ) || ( pList->labelEdit.iItem == cd->nmcd.dwItemSpec ) ) {
										SetTextColor( cd->nmcd.hdc, GetSysColor( COLOR_HIGHLIGHTTEXT ) );
										FillRect( cd->nmcd.hdc, &rc, GetSysColorBrush( COLOR_HIGHLIGHT ) );
									}
									// draw background of unselected item
									else {
										SetTextColor( cd->nmcd.hdc, 
											( pItem->wFlags & CTRLF_CHANGED ) 
												? clrChanged : ( pItem->wFlags & CTRLF_HASMETA )
													? clrMeta : ( ( pItem->wFlags & ( CTRLF_HASCUSTOM ) ) && ( pItem->wFlags & CTRLF_HASPROTO ) )
														? clrBoth : ( pItem->wFlags & CTRLF_HASCUSTOM )
															? clrCustom	: clrNormal );
										FillRect( cd->nmcd.hdc, &rc, GetSysColorBrush( COLOR_WINDOW ) );
									}
									SetWindowLongPtr( hDlg, DWL_MSGRESULT, CDRF_NEWFONT|CDRF_NOTIFYSUBITEMDRAW );
									return TRUE;
								
								case CDDS_SUBITEM|CDDS_ITEMPREPAINT:
								{
									HFONT hoFont = ( HFONT )SelectObject( cd->nmcd.hdc, pList->hFont );

									ListView_GetSubItemRect( cd->nmcd.hdr.hwndFrom, cd->nmcd.dwItemSpec, cd->iSubItem, LVIR_BOUNDS, &rc );
									if( cd->iSubItem == 0 ) {
										RECT rc2;
										ListView_GetSubItemRect( cd->nmcd.hdr.hwndFrom, cd->nmcd.dwItemSpec, 1, LVIR_BOUNDS, &rc2 );
										rc.right = rc2.left;
									}
									rc.left += 3;
									DrawText( cd->nmcd.hdc,
										pItem->pszText[cd->iSubItem] 
											? pItem->pszText[cd->iSubItem] 
											: ( cd->iSubItem == 0 && pItem->idstrList && pItem->iListItem > 0 && pItem->iListItem < pItem->idstrListCount )
												? pItem->idstrList[pItem->iListItem].ptszTranslated
												: TranslateT( "<empty>" ),
										-1, &rc, DT_END_ELLIPSIS|DT_NOCLIP|DT_NOPREFIX|DT_SINGLELINE|DT_VCENTER );
									SetWindowLongPtr( hDlg, DWL_MSGRESULT, CDRF_SKIPDEFAULT );
									return TRUE;
								}
							} /* switch( cd->nmcd.dwDrawStage ) */
							break;
						} /* case NM_CUSTOMDRAW: */
					} /* ( (( LPNMHDR )lParam )->code ) */
					break;
				}
			}
			break; /* case WM_NOTIFY: */

		case WM_COMMAND:
		{
			switch( LOWORD( wParam ) ) {
				case BTN_ADD_intEREST:
					return ProfileList_AddNewItem( hDlg, ( LPLISTCTRL )GetUserData( hList ), &pFmt[2] );
				case BTN_ADD_AFFLIATION:
					return ProfileList_AddNewItem( hDlg, ( LPLISTCTRL )GetUserData( hList ), &pFmt[1] );
				case BTN_ADD_PAST:
					return ProfileList_AddNewItem( hDlg, ( LPLISTCTRL )GetUserData( hList ), &pFmt[0] );
				case BTN_EDIT_CAT:
					ProfileList_BeginLabelEdit( hList, ListView_GetSelectionMark( hList ), 0 );
					break;
				case BTN_EDIT_VAL:
					ProfileList_BeginLabelEdit( hList, ListView_GetSelectionMark( hList ), 1 );
					break;
				case BTN_DEL:
					if( IDYES == MsgBox( hDlg, MB_YESNO|MB_ICON_QUESTION, LPGENT( "Question" ), LPGENT( "Delete an entry" ), LPGENT( "Do you really want to delete this entry?") ) ) {
						INT iItem = ListView_GetSelectionMark( hList );
						LPLISTCTRL pList = ( LPLISTCTRL )GetUserData( hList );

						ProfileList_DeleteItem( hList, iItem );
						if( PtrIsValid( pList ) ) pList->wFlags |= CTRLF_CHANGED;
						SendMessage( GetParent( hDlg ), PSM_CHANGED, NULL, NULL );
						// check if to delete any devider
						if( !ProfileList_GetItemData( hList, iItem-- ) && !ProfileList_GetItemData( hList, iItem ) )
							ListView_DeleteItem( hList, iItem );
					}
					break;
			}
			break;
		}
	}
	return FALSE;
}
